package com.collins.io.nio.receive;

import com.collins.io.nio.config.Config;
import org.apache.commons.io.input.BufferedFileChannelInputStream;
import org.apache.log4j.Logger;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

public class ReceiveClient {
    private static final Logger logger = Logger.getLogger(ReceiveClient.class);
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static AtomicInteger count = new AtomicInteger(0);
    private static final String HOST = "127.0.0.1";
    private static final int TIMES = 10;
    private static final Charset CHARSET = StandardCharsets.UTF_8;

    public static void main(String[] args) throws IOException {
        // sendFromProgram();
        // sendFromFile(HOST, Config.SERVER_PORT, Config.SOCKET_SEND_PATH);
        sendFromFileOp(HOST, Config.SERVER_PORT, Config.SOCKET_SEND_PATH);
    }

    public static void sendFromFile(String host, int port, String path)
            throws IOException {
        File directory = new File(path);
        File[] files = directory.listFiles();
        for (File file : files) {
            // INFO each file corresponds to one channel
            SocketChannel channel = connect(host, port);
            // INFO send file name
            String fileName = file.getName();
            sendFileNameLength(channel, fileName);
            sendFileName(channel, fileName);
            // INFO send file content
            InputStreamReader isr = new InputStreamReader(new FileInputStream(file), CHARSET);
            ByteBuffer buffer = ByteBuffer.allocate(Config.SERVER_BUFFER_SIZE);
            StringBuffer sb = new StringBuffer();
            int c= 0;
            while( (c = isr.read()) != -1) {
                sb.append((char) c);
            }
            String s = sb.toString();
            long contentLength = (long)s.length();
            // TODO write content length
            buffer.putLong(contentLength);
            buffer.flip();
            channel.write(buffer);
            buffer.clear();
            // TODO write content
            buffer.put(s.getBytes(CHARSET));
            buffer.flip();
            channel.write(buffer);
            buffer.clear();
            isr.close();
            shutdown(channel);
        }
    }

    public static void sendFromFileOp(String host, int port, String path)
            throws IOException {
        File directory = new File(path);
        File[] files = directory.listFiles();
        for (File file : files) {
            // INFO each file corresponds to one channel
            SocketChannel channel = connect(host, port);
            // INFO send file name
            String fileName = file.getName();
            sendFileNameLength(channel, fileName);
            sendFileName(channel, fileName);
            // INFO send file content
            FileInputStream fis = new FileInputStream(file);
            ByteBuffer buffer = ByteBuffer.allocate(Config.SERVER_BUFFER_SIZE);
            byte[] bytes = new byte[Config.SERVER_BUFFER_SIZE];
            int len = 0;
            while((len = fis.read(bytes)) != -1) {
                // TODO write content length
                buffer.putLong((long)len);
                buffer.flip();
                channel.write(buffer);
                buffer.clear();
                // LOG
                logger.debug(sdf.format(new Date(System.currentTimeMillis()))
                    + " writes " + len + " bytes ...");
                // TODO write content
                buffer.put(bytes, 0, len);
                buffer.flip();
                channel.write(buffer);
                buffer.rewind();
                buffer.clear();
            }
            fis.close();
            shutdown(channel);
        }
    }

    public static void sendFromProgram()
            throws IOException {
        for (int i = 0; i < TIMES; i++) {
            send(HOST, Config.SERVER_PORT);
        }
    }

    public static void send(String host, int port) throws IOException {
        String fileName = "file-" + count.getAndIncrement() + ".txt";
        String file = "this is simulated file content!";
        SocketChannel channel = connect(host, port);
        sendFileNameLength(channel, fileName);
        sendFileName(channel, fileName);
        sendFileLength(channel, file);
        sendFile(channel, file);
        shutdown(channel);
    }

    public static SocketChannel connect(String host, int port) throws IOException {
        InetSocketAddress address = new InetSocketAddress(host, port);
        // TODO 1 gain channel
        SocketChannel socketChannel = SocketChannel.open(address);
        socketChannel.configureBlocking(false);
        // TODO 2 spinning for connection establishment
        while (!socketChannel.finishConnect()) ;
        logger.info(sdf.format(new Date(System.currentTimeMillis()))
                + " connection success: " + host + ":" + port);
        return socketChannel;
    }

    public static void shutdown(SocketChannel channel) throws IOException {
        channel.shutdownOutput();
        channel.close();
    }

    public static void sendFileNameLength(SocketChannel channel, String fileName)
            throws IOException {
        // TODO 3 prepare data and send to server
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // INFO send file name
        int fileNameLength = fileName.length();
        buffer.putInt(fileNameLength);
        buffer.flip();
        channel.write(buffer);
        logger.debug(sdf.format(new Date(System.currentTimeMillis())) +
                " success write file name length: " + fileNameLength);
        buffer.clear();

    }

    public static void sendFileName(SocketChannel channel, String fileName)
            throws IOException {
        // TODO 3 prepare data and send to server
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // INFO send file name
        buffer.put(fileName.getBytes());
        buffer.flip();
        channel.write(buffer);
        logger.debug(sdf.format(new Date(System.currentTimeMillis())) +
                " success write file name: " + fileName);
        buffer.clear();
    }

    public static void sendFileLength(SocketChannel channel, String file)
            throws IOException {
        // TODO 3 prepare data and send to server
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // INFO send file content length
        long contentLength = (long) file.length();
        buffer.putLong(contentLength);
        buffer.flip();
        channel.write(buffer);
        logger.debug(sdf.format(new Date(System.currentTimeMillis())) +
                " success write file content length: " + contentLength);
        buffer.clear();
    }

    public static void sendFile(SocketChannel channel, String file)
            throws IOException {
        // TODO 3 prepare data and send to server
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // INFO send file content length
        buffer.put(file.getBytes());
        buffer.flip();
        channel.write(buffer);
        logger.debug(sdf.format(new Date(System.currentTimeMillis())) +
                " success write file content... ");
        buffer.clear();
    }
}
